package com.altio.swing;


import java.awt.Component;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

/**
 * Manages the selection of a set of controls. Implements SelectableComponent
 * and SelectionActionListener so that it can be treated as selectable amongst a
 * set of other SelectionManagers.
 * 
 * @author tomm
 * @altioapi all
 * 
 */
public class SelectionManager extends DragSelectListener implements SelectableComponent, SelectionActionListener {
  /** All the components that can be selected (Component->SelectableComponent) */
  private HashMap allComponents = new HashMap();

  /** All the components that are currently selected (SelectableComponent) */
  private Vector selectedComponents = new Vector();

  /** The control that is currently highlighted */
  private SelectableComponent highlightedComponent = null;

  /** True if this selection manager is itself selected and is therefore active */
  private boolean selected = false;

  /** The container that contains the controls to be selected */
  private WeakReference container;

  private Vector selectionListeners = new Vector();

  private Component dragComponent;

  private boolean dragSelectEnabled;

  private boolean highlightingEnabled;
  
  private MouseAdapter _mouseListener = null;

  /**
   * 
   * @param container
   * @param enableDragSelect
   * @param highlightingEnabled
   */
  public SelectionManager(Component container, boolean enableDragSelect, boolean highlightingEnabled) {
    super(new DragBox());

    dragComponent = getDragComponent();
    dragComponent.setVisible(false);

    setContainer(container);
    container.addMouseListener(this);
    container.addMouseMotionListener(this);

    dragSelectEnabled = enableDragSelect;
    this.highlightingEnabled = highlightingEnabled;

    /**
     * Add a listener for activating this manager
     */
    _mouseListener = (new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        selectActionPerformed(SelectionManager.this, e.isControlDown());
      }
    });
    container.addMouseListener(_mouseListener);
    addComponent(this);
  }
  
  /**
   * Remove all the listeners and components from a given container
   * @param container container to remove from
   */
  public void removeFromContainer(Container container) {
    container.removeMouseListener(this);
    container.removeMouseMotionListener(this);
    container.removeMouseListener(_mouseListener);
    container.remove(getDragComponent());
  }

  /**
   * Wrap WeakReference
   * 
   * @param c
   */
  private void setContainer(Component c) {
    container = new WeakReference(c);
  }

  /**
   * Wrap WeakReference
   * 
   * @return
   */
  private Component getContainer() {
    if (null != container) {
      return (Component)container.get();
    }
    return null;
  }
  
  /**
   * Adds a component to this manager
   * 
   * @param comp
   *          component to add
   */
  public void addComponent(SelectableComponent comp) {
    allComponents.put(comp.getComponent(), comp);
    comp.addSelectionActionListener(this);
  }

  /**
   * Updates all components according to whether this manager is selected and
   * each component is selected or highlighted.
   */
  protected void updateComponents() {
    // For each component
    Iterator nextComponent = ((HashMap) allComponents.clone()).keySet().iterator();
    while (nextComponent.hasNext()) {
      SelectableComponent currentComponent = (SelectableComponent) allComponents.get(nextComponent.next());

      // Is this not this component (i.e the container)
      if (currentComponent != this) {
        // Set the component as selected if this manager is selected and the
        // control is one of the
        // selected controls
        currentComponent.setSelected(selected && selectedComponents.contains(currentComponent));

        // Set this control as highlighted if it is the highlighted control
        currentComponent.setHighlighted((currentComponent == highlightedComponent));
      }
    }
  }

  public Component getComponent() {
    return getContainer();
  }

  public void setHighlighted(boolean highlighted) {
    // Do nothing
  }

  public void setSelected(boolean selected) {
    if (!selected == this.selected) {
      this.selected = selected;
      if (!selected) {
        selectedComponents.clear();
      }
      updateComponents();
    }
  }

  public Collection getSelectedComponents() {
    return (Collection) selectedComponents.clone();
  }

  public void setSelectedComponents(Collection selectedComponents) {
    this.selectedComponents.clear();
    this.selectedComponents.addAll(selectedComponents);
    updateComponents();
  }

  public void reset() {
    selectedComponents.clear();
  }

  public void addSelectionActionListener(SelectionActionListener listener) {
    selectionListeners.add(listener);
  }
  
  /**
   * PR 2147
   */
  public void removeSelectionActionListener(SelectionActionListener listener) {
    selectionListeners.remove(listener);
  }


  public void highlightActionPerformed(SelectableComponent component, boolean highlighted) {
    // Is the control being highlighted
    if (highlighted && !isDragging() && isHighlightingEnabled()) {
      // Change the highlighted component to this component
      highlightedComponent = component;
    }
    else {
      if (component == highlightedComponent) {
        // Change the highlighted component to this component
        highlightedComponent = null;
      }
    }

    // Update what is highlighted
    updateComponents();
  }

  public void selectActionPerformed(SelectableComponent component, boolean multiselect) {
    notifySelected();
    // Has the container been clicked?
    if (component == SelectionManager.this) {
      if (!multiselect) {
        // Deselect everything
        selectedComponents.clear();
      }
    }
    // Is the user selecting multiple coomponent
    else if (multiselect) {
      // If the component is already selected
      if (selectedComponents.contains(component)) {
        // Deselect the component
        selectedComponents.remove(component);
      }
      else {
        // Select the component
        selectedComponents.add(component);
      }
    }
    else {
      // If the component isn't already selected
      if (!selectedComponents.contains(component)) {
        // Deselect everything
        selectedComponents.clear();
        // Select the component
        selectedComponents.add(component);
      }
    }

    // Update what is selected
    updateComponents();
  }

  /**
   * Notifies all listeners that this manager has been selected
   */
  private void notifySelected() {
    Iterator nextListener = ((Vector) selectionListeners.clone()).iterator();
    while (nextListener.hasNext()) {
      SelectionActionListener currentListener = (SelectionActionListener) nextListener.next();

      if (currentListener != this) {
        currentListener.selectActionPerformed(this, false);
      }
    }
  }

  public boolean isHighlighted() {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean isSelected() {
    return selected;
  }

  public void mouseDragged(MouseEvent e) {
    if (isDragSelectEnabled()) {
      Rectangle selectionBounds = getDragComponent().getBounds();

      if (!e.isControlDown()) {
        // Deselect everything
        selectedComponents.clear();
      }

      // For each component
      Iterator nextComponent = ((HashMap) allComponents.clone()).keySet().iterator();
      while (nextComponent.hasNext()) {
        SelectableComponent currentComponent = (SelectableComponent) allComponents.get(nextComponent.next());

        // Is this not this component (i.e the container)
        if (currentComponent != this) {
          Rectangle currentBounds = currentComponent.getComponent().getBounds();

          if (selectionBounds.contains(currentBounds) && !selectedComponents.contains(currentComponent)) {
            selectedComponents.add(currentComponent);
          }
        }
      }

      notifySelected();
      updateComponents();
      super.mouseDragged(e);
    }
  }

  public boolean isDragSelectEnabled() {
    return dragSelectEnabled;
  }

  public void setDragSelectEnabled(boolean dragSelectEnabled) {
    this.dragSelectEnabled = dragSelectEnabled;
  }

  public boolean isHighlightingEnabled() {
    return highlightingEnabled;
  }

  public void setHighlightingEnabled(boolean highlightingEnabled) {
    this.highlightingEnabled = highlightingEnabled;
  }

  public void removeComponent(Component comp) {
    SelectableComponent toRemove = getSelectableComponent(comp);

    if (toRemove != null) {
      removeComponent(toRemove);
    }
  }

  public void removeComponent(SelectableComponent comp) {
    // PR 2147 - removing from Map using the same key we added with helps enormously
    allComponents.remove(comp.getComponent());
    comp.removeSelectionActionListener(this);

    selectedComponents.remove(comp);
    if (highlightedComponent == comp) {
      highlightedComponent = null;
    }

    updateComponents();
  }
  
  /**
   * PR 2147 - Cleanup
   */
  public final void destroy() {
    Component c = getContainer();
    if (null != c) {
      c.removeMouseListener(this);
      c.removeMouseMotionListener(this);          
    }
    
    removeComponent(this); // undo addition in constructor to get rid of listener too
    allComponents.clear();
    selectedComponents.clear();
  }

  public SelectableComponent getSelectableComponent(Component component) {
    SelectableComponent result = null;

    // For each component
    Iterator nextComponent = ((HashMap) allComponents.clone()).keySet().iterator();
    while (nextComponent.hasNext()) {
      SelectableComponent currentComponent = (SelectableComponent) allComponents.get(nextComponent.next());

      if (currentComponent.getComponent().equals(component)) {
        result = currentComponent;
        break;
      }
    }

    return result;
  }
  
  public void mouseReleased(MouseEvent e) {
    super.mouseReleased(e);
    
    // Make sure the selected components is definitely correct.
    updateComponents();
  }
}
